<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>天空盒</title>
    <meta name="description" content="WebGL skybox">
</head>

<body>
    <canvas id="myGLCanvas" width="500" height="500"></canvas>
    <script src="./js/gl-matrix.js"></script>
    <script id="VertexShader" type="x-shader/x-vertex">
        uniform mat4 projection;
        uniform mat4 modelview;
        attribute vec3 coords; 
        varying vec3 vCoords; 
        void main() { 
            vec4 eyeCoords = modelview * vec4(coords,1.0); 
            gl_Position = projection * eyeCoords; 
            vCoords = coords; 
        }
    </script>
    <script id="FragmentShader" type="x-shader/x-fragment">
        precision mediump float; 
        varying vec3 vCoords; 
        uniform samplerCube skybox; 
        void main() { 
            gl_FragColor = textureCube(skybox, vCoords); 
        }
    </script>
    <script>
    var skyboxImg = [
        './img/right.jpg',
        './img/left.jpg',
        './img/top.jpg',
        './img/down.jpg',
        './img/back.jpg',
        './img/front.jpg'
    ];

    var gl;
    var canvas;
    var shaderProgram;

    var aCoords;
    var uProjection;
    var uModelview;

    var projection = mat4.create();
    var modelview = mat4.create();

    var cube;

    // 旋转角度
    var currentAngle = [0.0, 0.0];

    function createGLContext(canvas) {
        var names = ["webgl", "experimental-webgl"];
        var context = null;
        for (var i = 0; i < names.length; i++) {
            try {
                context = canvas.getContext(names[i]);
            } catch (e) {}

            if (context) {
                break;
            }
        }
        if (context) {
            context.viewportWidth = canvas.width;
            context.viewportHeight = canvas.height;
        } else {
            alert("Failed to create WebGL context!");
        }
        return context;
    }

    function loadShaderFromDOM(id) {
        var shaderScript = document.getElementById(id);

        if (!shaderScript) {
            return null;
        }

        var shaderSource = "";
        var currentChild = shaderScript.firstChild;
        while (currentChild) {
            if (currentChild.nodeType == 3) {
                shaderSource += currentChild.textContent;
            }
            currentChild = currentChild.nextSibling;
        }

        var shader;
        if (shaderScript.type === "x-shader/x-fragment") {
            shader = gl.createShader(gl.FRAGMENT_SHADER);
        } else if (shaderScript.type === "x-shader/x-vertex") {
            shader = gl.createShader(gl.VERTEX_SHADER);
        } else {
            return null;
        }

        gl.shaderSource(shader, shaderSource);
        gl.compileShader(shader);

        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            alert(gl.getShaderInfoLog(shader));
            return null;
        }
        return shader;
    }

    function setupShaders() {
        var fragmentShader = loadShaderFromDOM("FragmentShader");
        var vertexShader = loadShaderFromDOM("VertexShader");

        shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);

        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
            alert("Failed to setup shaders");
        }

        gl.useProgram(shaderProgram);
    }

    function createModel() {
        // Create a cube
        //    v6----- v5
        //   /|      /|
        //  v1------v0|
        //  | |     | |
        //  | v7----|-v4
        //  |/      |/
        //  v2------v3
        var modelData = {};
        // 顶点坐标
        modelData.vertexPositions = new Float32Array([ // Vertex coordinates
            1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, // v0-v1-v2-v3 front
            1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, // v0-v3-v4-v5 right
            1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, // v0-v5-v6-v1 up
            -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, // v1-v6-v7-v2 left
            -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, // v7-v4-v3-v2 down
            1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0 // v4-v7-v6-v5 back
        ]);
        // 顶点索引
        modelData.indices = new Uint16Array([ // Indices of the vertices
            0, 1, 2, 0, 2, 3, // front
            4, 5, 6, 4, 6, 7, // right
            8, 9, 10, 8, 10, 11, // up
            12, 13, 14, 12, 14, 15, // left
            16, 17, 18, 16, 18, 19, // down
            20, 21, 22, 20, 22, 23 // back
        ]);

        var model = {};

        model.coordsBuffer = gl.createBuffer();
        model.indexBuffer = gl.createBuffer();
        model.count = modelData.indices.length;

        gl.bindBuffer(gl.ARRAY_BUFFER, model.coordsBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, modelData.vertexPositions, gl.STATIC_DRAW);

        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, model.indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, modelData.indices, gl.STATIC_DRAW);

        model.render = function() {
            gl.bindBuffer(gl.ARRAY_BUFFER, this.coordsBuffer);
            gl.vertexAttribPointer(aCoords, 3, gl.FLOAT, false, 0, 0);
            gl.uniformMatrix4fv(uModelview, false, modelview);
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
            gl.drawElements(gl.TRIANGLES, this.count, gl.UNSIGNED_SHORT, 0);
        }
        return model;
    }

    function setupBuffers() {
        aCoords = gl.getAttribLocation(shaderProgram, "coords");
        uModelview = gl.getUniformLocation(shaderProgram, "modelview");
        uProjection = gl.getUniformLocation(shaderProgram, "projection");

        // 开启attribute变量，使顶点着色器能访问缓冲区数据
        gl.enableVertexAttribArray(aCoords);
        gl.enable(gl.DEPTH_TEST);
        cube = createModel();
    }

    function setupTextures() {
        var count = 0;
        var img = new Array(6);
        for (var i = 0; i < 6; i++) {
            img[i] = new Image();
            img[i].onload = function() {
                count++;
                if (count === 6) {
                    var texture = gl.createTexture();
                    gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);

                    var targets = [
                        gl.TEXTURE_CUBE_MAP_POSITIVE_X, gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
                        gl.TEXTURE_CUBE_MAP_POSITIVE_Y, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
                        gl.TEXTURE_CUBE_MAP_POSITIVE_Z, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
                    ];

                    for (var j = 0; j < 6; j++) {
                        gl.texImage2D(targets[j], 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img[j]);
                        gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
                        gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
                    }

                    gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
                    tick();
                }
            }
            img[i].src = skyboxImg[i];
        }
    }

    function drawScene() {
        gl.clearColor(0, 0, 0, 1);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        mat4.perspective(projection, Math.PI / 3, 1, .1, 2);
        gl.uniformMatrix4fv(uProjection, false, projection);

        var viewMatrix = mat4.create();
        mat4.lookAt(viewMatrix, [0, 0, 0], [0, 0, -1], [0, 1, 0]);

        var modelMatrix = mat4.create();
        mat4.translate(modelMatrix, modelMatrix, [0, 0, 0]);
        mat4.rotate(modelMatrix, modelMatrix, currentAngle[0], [1, 0, 0]);
        mat4.rotate(modelMatrix, modelMatrix, currentAngle[1], [0, 1, 0]);

        mat4.multiply(modelMatrix, viewMatrix, modelMatrix);
        mat4.copy(modelview, modelMatrix);

        cube.render();
    }

    function initEvents() {
        var lastX = -1;
        var lastY = -1;
        var dragging = false;

        canvas.onmousedown = function(ev) {
            var x = ev.clientX;
            var y = ev.clientY;
            var rect = ev.target.getBoundingClientRect();
            if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
                lastX = x;
                lastY = y;
                dragging = true;
            }
        };
        document.onmouseup = function(ev) {
            dragging = false;
        };
        document.onmousemove = function(ev) {
            var x = ev.clientX;
            var y = ev.clientY;
            if (dragging) {
                var factor = 1 / canvas.height;
                var dx = factor * (lastX - x);
                var dy = factor * (lastY - y);
                currentAngle[0] = Math.max(Math.min(currentAngle[0] + dy, Math.PI / 2), -Math.PI / 2);
                currentAngle[1] = currentAngle[1] + dx;
            }
            lastX = x, lastY = y;
        };
    }

    function tick() {
        drawScene();
        requestAnimationFrame(tick);
    }

    function init() {
        canvas = document.getElementById('myGLCanvas');
        gl = createGLContext(canvas);

        setupShaders();
        setupBuffers();
        setupTextures();

        initEvents();
    }

    init();
    </script>
</body>

</html>
